// Created: Dec 11, 2014
//
// Author: wjinwen.1988@gmail.com

#include "chi/base_main.h"

#include <signal.h>
#include <unistd.h>
#include <getopt.h>
#include <iostream>

#include <glog/logging.h>

#include "chi/config.h"
#include "chi/func.h"
#include "chi/directory.h"
#include "chi/types.h"

// 定义命令行参数
DEFINE_bool(daemon, false, "run daemon");
DEFINE_string(conf_file, "", "config file");
DEFINE_string(pid_file, "", "pid file of the process");

static void CoreDumpHandle(const char* data, int size) {
  // 记录core dump信息到日志文件中
  std::string core_dump(data, size);
  LOG(ERROR) << core_dump;
}

namespace chi {
#if defined(__DATE__) && defined(__TIME__) && defined(VERSION)
  static const char g_build_description[] = 
      "CHI, Version: " VERSION ", Build time: " __DATE__ " " __TIME__;
#else
  static const char g_build_description[] = "unknown";
#endif
    
BaseMain* BaseMain::instance_ = NULL;

static void CtrlcHandlerCallback(int sig) {
  BaseMain* service = BaseMain::instance();
  assert(service != 0);
  service->HandleInterrupt(sig);
}

BaseMain::BaseMain()
  : stop_(false),
    is_glog_init_(false),
    mutex_(),
    cond_(mutex_) {
  assert(instance_ == NULL);
  instance_ = this;
}

BaseMain::~BaseMain() {
  instance_ = NULL;

  if (is_glog_init_)
    google::ShutdownGoogleLogging();
}

int BaseMain::main(int argc, char* argv[]) {
  // 设置帮助信息
  SetUsageMessage();
  // 设置版本信息
  SetVersion();

  google::ParseCommandLineFlags(&argc, &argv, true);

  int ret = CHI_ERROR;
  if (FLAGS_conf_file.empty()) {
    std::cerr << "must be set config file!" << std::endl;
  } else {
    ret = start(argc, argv, FLAGS_daemon);
  }
  google::ShutDownCommandLineFlags();
  return ret;
}

BaseMain* BaseMain::instance() {
  return instance_;
}

int BaseMain::start(int argc, char* argv[], const bool daemon) {
  // load config file
  int32_t ret = CHI_ERROR;
  do {
    if (CHI_CONFIG.Load(FLAGS_conf_file.c_str()) != EXIT_SUCCESS) {
      std::cerr << "load config error, config file is "
                << FLAGS_conf_file << std::endl;
      break;
    }

    const char* app_name = google::ProgramInvocationShortName();
    // initialize glog
    if (InitializeGlog(app_name) != CHI_SUCCESS) break;

    // initialize pid file
    if (daemon && InitializePidFile(app_name) != CHI_SUCCESS) break;

    signal(SIGPIPE, SIG_IGN);
    int32_t pid = 0;
    if (daemon) {
      pid = Func::StartDaemon(pid_file_path_.c_str());
      LOG(INFO) << "the server run in daemon, pid: " << pid;
    }

    if (pid == 0) {
      signal(SIGHUP, CtrlcHandlerCallback);
      signal(SIGTERM, CtrlcHandlerCallback);
      signal(SIGINT, CtrlcHandlerCallback);

      if (run(argc, argv) != CHI_SUCCESS) {
        LOG(ERROR) << app_name << " initialize failure, exit.";
        break;
      }

      LOG(INFO) << app_name << " initialize successful, wait for shutdown";
      WaitForShutdown();
      destroy();
      RemovePidFile(app_name);
      LOG(INFO) << app_name << " destroyed successful";
    }
    ret = CHI_SUCCESS;
  } while (0);
  return ret;
} // end start

void BaseMain::stop() {
  shutdown();
}

int BaseMain::shutdown() {
  VLOG(10) << "notifyall";
  MutexLockGuard lock(mutex_);
  if (!stop_) {
    stop_ = true;
    cond_.NotifyAll();
  }
  return CHI_SUCCESS;
}

int BaseMain::WaitForShutdown() {
  VLOG(10) << "wait for shutdown";
  MutexLockGuard lock(mutex_);
  while (!stop_) {
    cond_.Wait();
  }
  return CHI_SUCCESS;
}

int BaseMain::HandleInterrupt(int sig) {
  LOG(INFO) << "receive sig: " << sig;
  switch (sig) {
  case SIGHUP:
    break;
  case SIGTERM:
  case SIGINT:
    stop();
    break;
  default:
    break;
  }
  return CHI_SUCCESS;
}

void BaseMain::SetUsageMessage() {
  std::string options =
    "Usage: \n"
    "Options:\n"
    "--help        show this message...\n"
    "--version     show program verson...\n"
    "--daemon      run as a daemon...\n"
    "--daemon=1  --daemon=true\n"
    "--nodaemon    run in current shell...\n"
    "--daemon=0  --daemon=false\n"
    "--pid_file    set pid file...\n";
  google::SetUsageMessage(options);
}

void BaseMain::SetVersion() {
  google::SetVersionString(g_build_description);
}


int BaseMain::InitializePidFile(const char* app_name) {
  pid_file_path_ = FLAGS_pid_file;
  if (FLAGS_pid_file.empty()) {
    pid_file_path_ = log_dir_;
    if (pid_file_path_[pid_file_path_.size()-1] != '/')
      pid_file_path_ += "/";
    pid_file_path_ += app_name;
    pid_file_path_ += ".pid";
  }

  int ret = CHI_SUCCESS;
  if (!Directory::IsDirectory(pid_file_path_.c_str())) {
    if (!Directory::CreateFullPath(pid_file_path_.c_str(), true)) {
      LOG(ERROR) << "create pid directory " << pid_file_path_ 
          << " error: " << strerror(errno);
      ret = CHI_ERROR;
    }
  }

  return ret;
}

int BaseMain::InitializeGlog(const char* app_name) {
  // 初始化glog
  google::InitGoogleLogging(app_name);
  is_glog_init_ = true;

  log_dir_ = CHI_CONFIG.GetString("log", "log_dir", ""); 
  // 如果没有配置日志输出目录，则把日志输出到/tmp目录下。
  if (log_dir_.empty() || log_dir_ == "") {
    log_dir_ = "/tmp";
  }
  FLAGS_log_dir = log_dir_;

  const int debug_level = 4;
  log_level_ = CHI_CONFIG.GetInt("log", "log_level", 0);
  if (log_level_ == debug_level) {
    FLAGS_minloglevel = google::INFO; 
    // 打印自定义日志等级小于这个级别的日志
    // 使用glog的这个功能实现动态debug日志显示
    FLAGS_v = 99;
  } 
  
  // 设置日志文件大小
  max_log_size_ = CHI_CONFIG.GetInt("log", "log_size", 0);
  if (max_log_size_ == 0) {
    max_log_size_ = 1;
    FLAGS_max_log_size = max_log_size_;
  }

  // 硬盘没有空间时，停止记录日志
  FLAGS_stop_logging_if_full_disk = true;
  // 缓存1s的日志
  FLAGS_logbufsecs = 1;

  std::string log_prefix = log_dir_;
  if (log_prefix[log_prefix.size()-1] != '/')
    log_prefix += "/";

  // 设置日志文件前缀
  google::SetLogDestination(google::INFO, 
                            std::string(log_prefix+"INFO_"+
                                        std::string(app_name)+"_").c_str());
  google::SetLogDestination(google::WARNING, 
                            std::string(log_prefix+"WARN_"+
                                        std::string(app_name)+"_").c_str());
  google::SetLogDestination(google::ERROR,
                            std::string(log_prefix+"ERROR_"+
                                        std::string(app_name)+"_").c_str());

  // 如果不是以守护进程方式启动，则把在日志同时输出到终端上 
  if (!FLAGS_daemon) {
    FLAGS_alsologtostderr = true;
  }

  // 捕抓core dumped，用于记录core dump信息到日志文件中
  google::InstallFailureSignalHandler();
  google::InstallFailureWriter(&CoreDumpHandle);

  return CHI_SUCCESS;
}

void BaseMain::RemovePidFile(const char* app_name) {
  UNUSED(app_name);
  const char* const pid_path = pid_file_path();
  if (NULL != pid_path && strlen(pid_path) > 0)
    unlink(pid_path);
}

}  // namespace chi
